/*
 * @Author: zhangshu
 * @email: shiliang.zhang@tuya.com
 * @LastEditors: Please set LastEditors
 * @file name: app_led.c
 * @Description: 
 * @Copyright: HANGZHOU TUYA INFORMATION TECHNOLOGY CO.,LTD
 * @Company: http://www.tuya.com
 * @Date: 2022-02-08
 * @LastEditTime: 2022-02-08
 */
//#include "tuya_hal_gpio.h"

#include "tal_memory.h"
#include "tal_sw_timer.h"
#include "tal_gpio.h"
#include "tal_network_mgr.h"
#include "tal_mf_test.h"

#include "app_common.h"
#include "app_led.h"

//led blink type number
#define LED_BLINK_TYPE_NUM      9
//blink forver times
#define LED_BLINK_FOREVER       0xFFFFFFFF
//blink forever count
#define LED_BLINK_FOREVER_COUNT 0xFFFF

//led state
typedef enum {
    LED_STATE_NONE,
    LED_STATE_ON,
    LED_STATE_OFF,
}LED_STATE_E;

//blink parameter
typedef struct {
    LED_BLINK_TYPE_E   type;       //blink type
    UINT8_T            start_off;  //start with off or not
    UINT16_T           count;      //blink count
    UINT16_T           on_ms;      //on time duration in ms
    UINT16_T           off_ms;     //off time duration in ms
} BLINK_PARAMETER_T;

//blink array include type and priority
typedef struct {
    LED_BLINK_TYPE_E   type;       //blink type
    UINT8_T            priority;   //blink priority
} BLINK_ARRAY_T;

//blink type and corresponding piority
STATIC CONST BLINK_ARRAY_T blink_array[LED_BLINK_TYPE_NUM] = {
    {LED_BLINK_TYPE_NONE,                    0xFF},
    {LED_BLINK_TYPE_POWER_ON,                   1},
    {LED_BLINK_TYPE_START_JOIN,                 3},
    {LED_BLINK_TYPE_JOIN_SUCCESS,               2},
    {LED_BLINK_TYPE_BUTTON_DOWN,                4},
    {LED_BLINK_TYEP_ALARM,                      6},
    {LED_BLINK_TYEP_TAMPER,                     7},
    {LED_BLINK_TYPE_IDENTIFY,                   5},
    {LED_BLINK_TYPE_MF_TEST ,                   0},
};

//blink para
STATIC BLINK_PARAMETER_T blink_parameter = {LED_BLINK_TYPE_NONE, 0, 0, 0, 0};
//led state
STATIC LED_STATE_E led_state = LED_STATE_NONE;
//led info
STATIC APP_LED_INFO_T *led_info;

//led blink event and handler
TIMER_ID etimer_led_blink_event_id;
STATIC VOID_T app_sensor_led_blink_event_handler(TIMER_ID timer_id, VOID_T *arg);

//led event create
STATIC VOID_T app_led_event_init(VOID_T);
//led stop all led
STATIC VOID_T led_stop(VOID_T);
//set status led gpio status
STATIC VOID_T status_led_set(BOOL_T onoff);
//set network led gpio status
STATIC VOID_T network_led_set(BOOL_T onoff);

extern BOOL_T  mf_test_doing_flag;
extern BOOL_T  sys_init_finish_flag;


/**
 * @description: led init
 * @param[in] info: led info
 * @return: OPERATE_RET, success when OPRT_OK, or else
 */
OPERATE_RET app_led_oem_init(APP_LED_INFO_T *info)
{
    STATIC UINT8_T is_init = 0;

    if (is_init || info == NULL) {
        USER_PR_DEBUG("gpio already init or info null\r\n");
        return OPRT_COM_ERROR;
    }

    led_info = (APP_LED_INFO_T *)tal_malloc(SIZEOF(APP_LED_INFO_T));

    if (led_info == NULL) {
        USER_PR_DEBUG("[malloc error] app led malloc error\r\n");
        return OPRT_MALLOC_FAILED;
    }

    is_init = 1;
    tal_system_memset(led_info, 0, SIZEOF(APP_LED_INFO_T));
    tal_system_memcpy(led_info, info, SIZEOF(APP_LED_INFO_T));

    TUYA_GPIO_BASE_CFG_T status_config;
    TUYA_GPIO_BASE_CFG_T net_config;

    tal_system_memset(&status_config, 0, SIZEOF(TUYA_GPIO_BASE_CFG_T));
    tal_system_memset(&net_config, 0, SIZEOF(TUYA_GPIO_BASE_CFG_T));

    if (led_info->status_led.enabled) {
        status_config.mode = TUYA_GPIO_PUSH_PULL;
        status_config.direct = TUYA_GPIO_OUTPUT;
        if (led_info->status_led.driver_flag) {
            status_config.level = TUYA_GPIO_LEVEL_LOW;
        } else {
            status_config.level = TUYA_GPIO_LEVEL_HIGH;
        }
        tal_gpio_init(led_info->status_led.pin_num, &status_config);
    }

    if (led_info->net_led.enabled) {
        net_config.mode = TUYA_GPIO_PUSH_PULL;
        net_config.direct = TUYA_GPIO_OUTPUT;
        if (led_info->net_led.driver_flag) {
            net_config.level = TUYA_GPIO_LEVEL_LOW;
        } else {
            net_config.level = TUYA_GPIO_LEVEL_HIGH;
        }
        tal_gpio_init(led_info->net_led.pin_num, &net_config);
    }


    app_led_event_init();
    USER_PR_DEBUG("led init OK!!!");
    return OPRT_OK;
}

/**
 * @description: led blink event create
 * @param[in] none
 * @return: none
 */
STATIC VOID_T app_led_event_init(VOID_T)
{
    tal_sw_timer_create(app_sensor_led_blink_event_handler, NULL, &etimer_led_blink_event_id);
}

/**
 * @description: set_blink_event
 * @param[in] type: blink type, refer to LED_BLINK_TYPE_E
 * @param[in] on_ms: on time of duration in ms
 * @param[in] off_ms: off time of duration in ms
 * @param[in] delay_ms: delay start blink
 * @param[in] start_off: start with off state or not
 * @return: none
 */
VOID_T set_blink_event(LED_BLINK_TYPE_E type, UINT16_T on_ms, UINT16_T off_ms, UINT32_T dur_ms, UINT16_T delay_ms, UINT8_T start_off)
{
    if (blink_array[type].priority > blink_array[blink_parameter.type].priority) {
        USER_PR_DEBUG("blink priority low, do not set blink\r\n");
        return;
    }

    led_state = LED_STATE_ON;
    tal_system_memset(&blink_parameter, 0, SIZEOF(BLINK_PARAMETER_T));
    led_stop();
    
    blink_parameter.type = type;
    blink_parameter.on_ms = on_ms;
    blink_parameter.off_ms = off_ms;
    blink_parameter.start_off = start_off;

    if (dur_ms == LED_BLINK_FOREVER) {
        blink_parameter.count = LED_BLINK_FOREVER_COUNT;
    } else {
        blink_parameter.count = (UINT16_T)(dur_ms/(on_ms + off_ms));
    }

    tal_sw_timer_start(etimer_led_blink_event_id, delay_ms, TAL_TIMER_ONCE);
    USER_PR_DEBUG("SET BLINK, on time %d, off time %d, count %d\r\n", blink_parameter.on_ms, blink_parameter.off_ms, blink_parameter.count);
}

/**
 * @description: led blink event
 * @param [in] timer_id: timer id
 * @param[in] args: null
 * @return: none
 */
STATIC VOID_T app_sensor_led_blink_event_handler(TIMER_ID timer_id, VOID_T *arg)
{
    if (blink_parameter.start_off) {
        blink_parameter.start_off = 0;
    	status_led_set(FALSE);
        network_led_set(FALSE);
        if((!mf_test_doing_flag) || (sys_init_finish_flag)){
            tkl_cpu_allow_sleep();
        }
        tal_sw_timer_start(etimer_led_blink_event_id, 200, TAL_TIMER_ONCE);
        return;
    }

    if (led_state == LED_STATE_ON) {
        led_state = LED_STATE_OFF;
        switch (blink_parameter.type) {
            case LED_BLINK_TYPE_POWER_ON:
            case LED_BLINK_TYPE_START_JOIN:
            case LED_BLINK_TYPE_JOIN_SUCCESS:
            case LED_BLINK_TYPE_BUTTON_DOWN: {
                network_led_set(TRUE);
                if((!mf_test_doing_flag) || (sys_init_finish_flag)){
                    tkl_cpu_force_wakeup();
                }
                USER_PR_DEBUG("NET LED on\r\n");
                break;
            }
            case LED_BLINK_TYEP_ALARM:
            case LED_BLINK_TYEP_TAMPER:{
                status_led_set(TRUE);
                if((!mf_test_doing_flag) || (sys_init_finish_flag)){
                    tkl_cpu_force_wakeup();
                }
                USER_PR_DEBUG("ALARM LED on\r\n");
                break;
            }
            case LED_BLINK_TYPE_MF_TEST: {
                network_led_set(TRUE);
                if((!mf_test_doing_flag) || (sys_init_finish_flag)){
                    tkl_cpu_force_wakeup();
                }
                status_led_set(FALSE);
                break;
            }
            default: {
                break;
            }
        }
        if (blink_parameter.on_ms != LED_BLINK_FOREVER_COUNT) {
            tal_sw_timer_start(etimer_led_blink_event_id, blink_parameter.on_ms, TAL_TIMER_ONCE);
        }
        return;
    } else if (led_state == LED_STATE_OFF) {
        if (blink_parameter.count != LED_BLINK_FOREVER_COUNT) {
            blink_parameter.count--;
        }
        switch (blink_parameter.type) {
            case LED_BLINK_TYPE_MF_TEST: {
                network_led_set(FALSE);
                status_led_set(TRUE);
                if((!mf_test_doing_flag) || (sys_init_finish_flag)){
                    tkl_cpu_force_wakeup();
                }
                break;
            }
            default: {
                USER_PR_DEBUG("ALL LED off\r\n");
                status_led_set(FALSE);
                network_led_set(FALSE);
                if((!mf_test_doing_flag) || (sys_init_finish_flag)){
                    tkl_cpu_allow_sleep();
                }
                break;
            }
        }
        if (blink_parameter.off_ms != LED_BLINK_FOREVER_COUNT) {
            tal_sw_timer_start(etimer_led_blink_event_id, blink_parameter.off_ms, TAL_TIMER_ONCE);
        }
        if (blink_parameter.count) {
            led_state = LED_STATE_ON;
            return;
        } else {
            led_state = LED_STATE_NONE;
        }
    } else {
        LED_BLINK_TYPE_E temp_type = blink_parameter.type;
        blink_parameter.type = LED_BLINK_TYPE_NONE;
        led_blink_complete_callback(temp_type);
    }
}

/**
 * @description: stop led blink
 * @param[in] none
 * @return: none
 */
VOID_T stop_blink(VOID_T)
{
    tal_sw_timer_stop(etimer_led_blink_event_id);
    led_state = LED_STATE_NONE;
    tal_system_memset(&blink_parameter, 0, SIZEOF(BLINK_PARAMETER_T));
    blink_parameter.type = LED_BLINK_TYPE_NONE;
    led_stop();
}

/**
 * @description: stop all led
 * @param[in] none
 * @return: none
 */
STATIC VOID_T led_stop(VOID_T)
{
    status_led_set(FALSE);
    network_led_set(FALSE);
    if((!mf_test_doing_flag) || (sys_init_finish_flag)){
        tkl_cpu_allow_sleep();
    }
}

/**
 * @description: power on solid indicate
 * @param[in] none
 * @return: none
 */
VOID_T dev_power_on_indicate(VOID_T)
{
    set_blink_event(LED_BLINK_TYPE_POWER_ON, 3000, 200, 3200, 0, 0);
}

/**
 * @description: device start join led indicate
 * @param[in] none
 * @return: none
 */
VOID_T dev_start_join_indicate(BOOL_T indicate)
{
    if (indicate) {
        set_blink_event(LED_BLINK_TYPE_START_JOIN, 300, 300, LED_BLINK_FOREVER, 0, 0);
    } else {
        if (blink_array[LED_BLINK_TYPE_START_JOIN].priority == blink_array[blink_parameter.type].priority) {
            stop_blink();
            led_blink_complete_callback(LED_BLINK_TYPE_START_JOIN);
        }
    }
}

/**
 * @description: device join network success led indicate
 * @param[in] none
 * @return: none
 */
VOID_T dev_join_success_indicate(VOID_T)
{
    set_blink_event(LED_BLINK_TYPE_JOIN_SUCCESS, 3000, 200, 3200, 0, 0);
}

/**
 * @description: device alarm indicate
 * @param[in] alarm: TRUE when alarm or not
 * @return: none
 */
VOID_T dev_alarm_indicate(BOOL_T alarm)
{
    if (alarm) {
        if (LED_INDICATE_TYPE_NONE == led_info->alarm_type) {
            return;
        }else if (LED_INDICATE_TYPE_ONCE == led_info->alarm_type) {
            set_blink_event(LED_BLINK_TYEP_ALARM, 300, 0, 300, 0, 0);
        } else if (LED_INDICATE_TYPE_SOLID == led_info->alarm_type) {
            set_blink_event(LED_BLINK_TYEP_ALARM, LED_BLINK_FOREVER_COUNT, 0, LED_BLINK_FOREVER, 0, 0);
        }
    } else {
        if (blink_array[LED_BLINK_TYEP_ALARM].priority == blink_array[blink_parameter.type].priority) {
            stop_blink();
            led_blink_complete_callback(LED_BLINK_TYEP_ALARM);
        }
    }
}

/**
 * @description: device tamper indicate
 * @param[in] tamper: TRUE when tamper or not
 * @return: none
 */
VOID_T dev_tamper_indicate(BOOL_T tamper)
{
    if (tamper) {
        if (LED_INDICATE_TYPE_NONE == led_info->tamper_type) {
            return;
        }else if (LED_INDICATE_TYPE_ONCE == led_info->tamper_type) {
            set_blink_event(LED_BLINK_TYEP_TAMPER, 300, 0, 300, 0, 0);
        } else if (LED_INDICATE_TYPE_SOLID == led_info->tamper_type) {
            set_blink_event(LED_BLINK_TYEP_TAMPER, LED_BLINK_FOREVER_COUNT, 0, LED_BLINK_FOREVER, 0, 0);
        }
    } else {
        if (blink_array[LED_BLINK_TYEP_TAMPER].priority == blink_array[blink_parameter.type].priority) {
            stop_blink();
            led_blink_complete_callback(LED_BLINK_TYEP_TAMPER);
        }
    }
}

/**
 * @description: button press indicate
 * @param[in] none
 * @return: none
 */
VOID_T button_press_on_indicate(VOID_T)
{
    set_blink_event(LED_BLINK_TYPE_BUTTON_DOWN, LED_BLINK_FOREVER_COUNT, 0, LED_BLINK_FOREVER, 0, 0);
}

/**
 * @description: button release indicate
 * @param[in] none
 * @return: none
 */
VOID_T button_release_off_indicate(VOID_T)
{
    if (blink_array[LED_BLINK_TYPE_BUTTON_DOWN].priority == blink_array[blink_parameter.type].priority) {
        stop_blink();
        led_blink_complete_callback(LED_BLINK_TYPE_BUTTON_DOWN);
    }
}

/**
 * @description: identify indicate
 * @param[in] time_ms: identify time in ms
 * @return: none
 */
VOID_T identify_indicate(UINT32_T time_ms)
{
    if (time_ms == LED_BLINK_FOREVER) {
        set_blink_event(LED_BLINK_TYPE_IDENTIFY, 500, 500, LED_BLINK_FOREVER, 0, 1);
    } else {
        set_blink_event(LED_BLINK_TYPE_IDENTIFY, 500, 500, time_ms, 0, 1);
    }
}

/**
 * @description: manufacture test blink
 * @param[in] cmd: manufacture command, refer to TUYA_MF_TEST_CMD_T
 * @return: none
 */
VOID_T mf_test_blink(TUYA_MF_TEST_CMD_T cmd)
{
    switch (cmd) {
        case TUYA_MF_TEST_LED_ON_ALL: {
            blink_parameter.type = LED_BLINK_TYPE_MF_TEST;
            network_led_set(TRUE);
            status_led_set(TRUE);
            if((!mf_test_doing_flag) || (sys_init_finish_flag)){
                tkl_cpu_force_wakeup();
            }
            break;
        }
        case TUYA_MF_TEST_LED_OFF_ALL: {
            stop_blink();
            break;
        }
        case TUYA_MF_TEST_LED_BLINK_ALL: {
            set_blink_event(LED_BLINK_TYPE_MF_TEST, 500, 500, LED_BLINK_FOREVER, 0, 1);
            break;
        }
    }
}

/**
 * @description: set alarm led on off state
 * @param[in] state: on off state
 * @return: none
 */
VOID_T set_alarm_led_on_off(BOOL_T alarm, UINT8_T state)
{
    if (state) {
        if (alarm && (LED_INDICATE_TYPE_SOLID == led_info->alarm_type)) {
            dev_alarm_indicate(TRUE);
        }
    }else {
        if (alarm && (LED_INDICATE_TYPE_SOLID == led_info->alarm_type) && (blink_parameter.type == LED_BLINK_TYEP_ALARM)) {
            stop_blink();
        }
    }
}

/**
 * @description: set status led on off state
 * @param[in] state: on off state
 * @return: none
 */
STATIC VOID_T status_led_set(BOOL_T onoff)
{
    if (led_info->status_led.driver_flag) {
        if (onoff) {
            tal_gpio_write(led_info->status_led.pin_num, TUYA_GPIO_LEVEL_HIGH);
        } else {
            tal_gpio_write(led_info->status_led.pin_num, TUYA_GPIO_LEVEL_LOW);
        }
    } else {
            if (onoff) {
                tal_gpio_write(led_info->status_led.pin_num, TUYA_GPIO_LEVEL_LOW);
            } else {
                tal_gpio_write(led_info->status_led.pin_num, TUYA_GPIO_LEVEL_HIGH);
            }
    }
}

/**
 * @description: set network led on off state
 * @param[in] state: on off state
 * @return: none
 */
STATIC VOID_T network_led_set(BOOL_T onoff)
{
    if (led_info->net_led.driver_flag) {
        if (onoff) {
            tal_gpio_write(led_info->net_led.pin_num, TUYA_GPIO_LEVEL_HIGH);
        } else {
            tal_gpio_write(led_info->net_led.pin_num, TUYA_GPIO_LEVEL_LOW);
        }
    } else {
            if (onoff) {
                tal_gpio_write(led_info->net_led.pin_num, TUYA_GPIO_LEVEL_LOW);
            } else {
                tal_gpio_write(led_info->net_led.pin_num, TUYA_GPIO_LEVEL_HIGH);
            }
    }
}

